/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny;

import com.softwarecraftsmen.orogeny.BuildScript.BuildSyntaxException;
import com.softwarecraftsmen.orogeny.actions.Action;
import com.softwarecraftsmen.orogeny.annotations.InternalApi;
import com.softwarecraftsmen.orogeny.buildEnvironments.BuildEnvironment;
import com.softwarecraftsmen.orogeny.buildLogs.IndentingBuildLog;
import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import com.softwarecraftsmen.orogeny.taskNames.NoKnownTaskExistsException;
import com.softwarecraftsmen.orogeny.taskNames.TaskName;
import com.softwarecraftsmen.orogeny.taskNames.TaskName.SomeTaskName;
import org.jetbrains.annotations.NotNull;

import java.util.*;

import static com.softwarecraftsmen.orogeny.buildLogs.Indentation.ActionIndentation;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.AllDetails;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Success;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Throwable;
import static com.softwarecraftsmen.orogeny.taskNames.TaskName.taskName;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Locale.UK;

public class Task
{
	private final TaskName name;
	private final Set<TaskName> dependsOn;
	private final List<Action> does;
	private boolean isHidden;

	public Task(final @NotNull SomeTaskName name)
	{
		this.name = name;
		this.dependsOn = new LinkedHashSet<>();
		this.does = new ArrayList<>();
		this.isHidden = false;
	}

	@NotNull
	public String toString()
	{
		return name.toString();
	}

	//TODO: Change access to hide from public API
	@InternalApi
	public void guardHasDependenciesAndDoesSomething()
	{
		if (does.size() == 0 && dependsOn.size() == 0)
		{
			throw new TaskDoesNothingException(name);
		}
	}

	//TODO: Change access to hide from public API
	@InternalApi
	public boolean isVisible()
	{
		return !isHidden;
	}

	//TODO: Change access to hide from public API
	@InternalApi
	public void originalDependentTasks(final @NotNull Queue<TaskName> tasksInExecutionOrder, final @NotNull Map<TaskName, Task> allTasks)
	{
		dependentTasks(tasksInExecutionOrder, allTasks, name);
	}

	private void dependentTasks(final @NotNull Queue<TaskName> tasksInExecutionOrder, final @NotNull Map<TaskName, Task> allTasks, final @NotNull TaskName originalTaskName)
	{
		if (tasksInExecutionOrder.contains(name))
		{
			return;
		}
		for (TaskName taskName : dependsOn)
		{
			if (taskName.equals(originalTaskName))
			{
				throw new BuildSyntaxException(format(UK, "The dependency '%1$s' of task '%2$s' is circularly dependent on initial task '%3$s'", taskName, name, originalTaskName));
			}
			try
			{
				final Task task = taskName.selectTask(allTasks);
				task.dependentTasks(tasksInExecutionOrder, allTasks, originalTaskName);
			}
			catch(NoKnownTaskExistsException noKnownTask)
			{
                System.out.println(allTasks.keySet());
				throw new BuildSyntaxException(format(UK, "The dependency '%1$s' of task '%2$s' is not known in the build script", taskName, name));
			}
		}
		tasksInExecutionOrder.offer(name);
	}


	//TODO: Change access to hide from public API
	@InternalApi
	public boolean allDependenciesSatisfied(final @NotNull Set<TaskName> completedTasks)
	{
		return completedTasks.containsAll(dependsOn);
	}

	//TODO: Change access to hide from public API
	@InternalApi
	public SuccessOrFailure execute(final @NotNull IndentingBuildLog indentingBuildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		SuccessOrFailure successOrFailure = Success;
		if (does.size() == 0)
		{
			indentingBuildLog.writeMessage(AllDetails, "(nothing to do)");
		}
		final Iterator<Action> actions = does.iterator();
		while (successOrFailure.isSuccess() && actions.hasNext())
		{
			final Action thingToDo = actions.next();
			indentingBuildLog.writeMessage(ActionIndentation, thingToDo.describeWhatWeDo());
			try
			{
				successOrFailure = thingToDo.execute(indentingBuildLog, buildEnvironment);
			}
			catch(Exception exception)
			{
				successOrFailure = Throwable(exception);
			}
			indentingBuildLog.writeMessage(ActionIndentation, successOrFailure);
		}
		return successOrFailure;
	}

	@NotNull
	public TaskDepends isHidden()
	{
		isHidden = true;
		return new TaskDepends();
	}

	@NotNull
	public TaskDepends dependsOn(final @NotNull String... names)
	{
		for (String name : names)
		{
			final SomeTaskName taskName = taskName(name);
			if (taskName.equals(this.name))
			{
				throw new BuildSyntaxException(format(UK, "Task %1$s can not depend on its self - check dependsOn()", name));
			}
			dependsOn.add(taskName);
		}
		return new TaskDepends();
	}

	@NotNull
	public TaskDoes does(final @NotNull Action... actions)
	{
		if (actions.length == 0)
		{
			throw new ThereMustBeMoreThanOneThingToDoForATaskException();
		}
		does.addAll(asList(actions));
		return new TaskDoes();
	}

	public final class TaskDepends
	{
		private TaskDepends()
		{}

		@NotNull
		public TaskDepends dependsOn(final @NotNull String name)
		{
			return Task.this.dependsOn(name);
		}

		@NotNull
		public TaskDoes does(final @NotNull Action... things)
		{
			if (things.length == 0)
			{
				throw new ThereMustBeMoreThanOneThingToDoForATaskException();
			}
			does.addAll(asList(things));
			return new TaskDoes();
		}
	}

	public final class TaskDoes
	{
		private TaskDoes()
		{}
	}

	public static final class ThereMustBeMoreThanOneThingToDoForATaskException extends BuildSyntaxException
	{
		public ThereMustBeMoreThanOneThingToDoForATaskException()
		{
			super("It does not make sense to not have anything for a task to do");
		}
	}

	public static final class TaskDoesNothingException extends BuildSyntaxException
	{
		public TaskDoesNothingException(final @NotNull TaskName taskName)
		{
			super(format(UK, "Task %1$s does nothing", taskName));
		}
	}
}
